#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "shared.h"
#include "flit.h"
#include "flit_queue.h"
#include "osf.h"

void sim_init(int argc, char *argv[])
{
  int node, chn, time, queue, num_opt; 

  //Seed the random number generator 
  srand(0);

  //Initialize flit pool
  init_flit_pool(); 
   
  printf("Simulation options:"); 
  for(num_opt = 1;num_opt < argc;)
  { 
    if(strstr(argv[num_opt], "#")) 
    { 
      printf("\nIgnoring Comment %s\n", argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-nn")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      NUM_NODES = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-nc")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      NUM_CHN = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-cft")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      CHN_FLY_TIME = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-max-flts")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      NUM_FLT_TO_INJ = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-rate")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      INJ_RATE = atof(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-ibd")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      INJ_BUF_DEPTH = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-ebd")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      EJT_BUF_DEPTH = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-pd")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      SWITCH_PROC_DLY = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-frame-size")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      FRAME_SZ = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-tt")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      if(strstr(argv[num_opt], "BERN"))
        TRA_TYPE = BERN; 
      else if(strstr(argv[num_opt], "POIS"))
        TRA_TYPE = POIS; 
      else 
      { 
        printf("\nUnknown traffic type:%s\n", argv[num_opt]); 
        exit(1); 
      } 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-tp")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      if(strstr(argv[num_opt], "UNIF"))
        TRA_PATTERN = UNIF; 
      else if(strstr(argv[num_opt], "HOTS"))
        TRA_PATTERN = HOTS; 
      else if(strstr(argv[num_opt], "TRAN"))
        TRA_PATTERN = TRAN; 
      else 
      { 
        printf("\nUnknown traffic pattern:%s\n", argv[num_opt]);
        exit(1); 
      } 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-max-reqs")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      MAX_REQS = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-max-accs")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      MAX_ACCS = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-idl-lat")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      IDL_LAT = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else if(strstr(argv[num_opt], "-qos")) 
    {
      printf("%s", argv[num_opt]);
      QOS = true;  
      num_opt ++;
    } 
    else if(strstr(argv[num_opt], "-v")) 
    {
      printf("%s", argv[num_opt]);
      VERBOSE = true;  
      num_opt ++;
    }
    else if(strstr(argv[num_opt], "-stat")) 
    {
      printf("%s", argv[num_opt]); 
      num_opt ++; 
      printf("%s", argv[num_opt]);
      STAT_INTV = atoi(argv[num_opt]); 
      num_opt ++; 
    }
    else
    { 
      printf("\nUnknown option:%s\n", argv[num_opt]); 
      exit(1); 
    } 
  } 
  
  SRC_CLK    = 0;
  DLY_TAP_PT = 0;  
  CHN_PHASE  = (double) CHN_FLY_TIME / (double) NUM_NODES; 

  //Allocate injeciton and ejection queues 
  inj_queues    = (flit_queue_t **)malloc(NUM_NODES * sizeof(flit_queue_t *));
  if(inj_queues == NULL) 
  {
    perror("Fail to allocate injection queues"); 
    exit(1); 
  }  
  inj_queues[0] = 
    (flit_queue_t *)malloc(NUM_NODES * NUM_CHN * sizeof(flit_queue_t)); 

  if(inj_queues[0] == NULL) 
  { 
    perror("Fail to allocate injection queues"); 
    exit(1); 
  } 
  for(node = 0;node < NUM_NODES;node ++)
  { 
    inj_queues[node] = inj_queues[0] + node * NUM_CHN;
    for(chn = 0;chn < NUM_CHN;chn ++)
      init_queue(&inj_queues[node][chn]); 
  } 

  ejt_queues = (flit_queue_t *)malloc(NUM_CHN * sizeof(flit_queue_t));
  for(chn = 0;chn < NUM_CHN;chn ++)
    init_queue(&ejt_queues[chn]);

  //Initialize default channel and priority map
  chn_map = (unsigned int **)malloc(NUM_CHN * sizeof(unsigned int *)); 
  if(chn_map == NULL) 
  {
    perror("Fail to allocate channel map"); 
    exit(1); 
  }  
  chn_map[0] = 
    (unsigned int *)malloc(NUM_CHN * NUM_NODES * sizeof(unsigned int));  
  if(chn_map[0] == NULL) 
  {
    perror("Fail to allocate channel map"); 
    exit(1); 
  }
  for(chn = 0;chn < NUM_CHN;chn ++) 
  {
    chn_map[chn] = chn_map[0] + chn * NUM_NODES; 
    for(node = 0;node < NUM_NODES;node ++)
      chn_map[chn][node] = (chn + node) % NUM_NODES;
  } 

  node_pri_map    = (unsigned int **)malloc(NUM_NODES * sizeof(unsigned int *));
  if(node_pri_map == NULL) 
  {
    perror("Fail to allocate priority maps"); 
    exit(1); 
  }  
  node_pri_map[0] = 
    (unsigned int *)malloc(NUM_NODES * NUM_CHN * sizeof(unsigned int)); 

  if(node_pri_map[0] == NULL) 
  { 
    perror("Fail to allocate priority maps"); 
    exit(1); 
  }
  for(node = 0;node < NUM_NODES;node ++)
  { 
    node_pri_map[node] = node_pri_map[0] + node * NUM_CHN; 
    for(chn = 0;chn < NUM_CHN;chn ++)
      node_pri_map[node][chn] = (node + NUM_NODES - chn) % NUM_NODES; 
  }

  //Initialize request vector
  requests = (unsigned int **)malloc(NUM_CHN * sizeof(unsigned int *)); 
  if(requests == NULL) 
  {
    perror("Fail to allocate request vector"); 
    exit(1); 
  }  
  requests[0] = 
    (unsigned int *)malloc(NUM_CHN * NUM_NODES * sizeof(unsigned int));  
  if(requests[0] == NULL) 
  {
    perror("Fail to allocate request vector"); 
    exit(1); 
  }
  for(chn = 0;chn < NUM_CHN;chn ++) 
  {
    requests[chn] = requests[0] + chn * NUM_NODES; 
    for(node = 0;node < NUM_NODES;node ++)
      requests[chn][node] = 0;
  }

  //Initialize token vector 
  token = (unsigned int *)malloc(NUM_CHN * sizeof(unsigned int));
  for(chn = 0;chn < NUM_CHN;chn ++)
     token[chn] = 0;

  //Initialize last_requested and last_used vector 
  last_requested = (unsigned int *)malloc(NUM_NODES * sizeof(unsigned int));
  for(node = 0;node < NUM_NODES;node ++)
     last_requested[node] = 0;

  last_used = (unsigned int *)malloc(NUM_NODES * sizeof(unsigned int));
  for(node = 0;node < NUM_NODES;node ++)
     last_used[node] = 0;

  //Initialize delay tap 
  flt_dly_tap = (flit_t ***)malloc(NUM_CHN * sizeof(flit_t **)); 
  if(flt_dly_tap == NULL) 
  {
    perror("Fail to allocate flit delay tap"); 
    exit(1); 
  }  
  flt_dly_tap[0] = 
    (flit_t **)malloc(NUM_CHN * CHN_FLY_TIME * sizeof(flit_t *));  
  if(flt_dly_tap[0] == NULL) 
  {
    perror("Fail to allocate flit delay tap"); 
    exit(1); 
  }
  for(chn = 0;chn < NUM_CHN;chn ++) 
  {
    flt_dly_tap[chn] = flt_dly_tap[0] + chn * CHN_FLY_TIME; 
    for(time = 0;time < CHN_FLY_TIME; time ++)
      flt_dly_tap[chn][time] = NULL;
  }

  //Initialize channel status and completion counters */
  comp_counter = (unsigned int *)malloc(NUM_CHN * sizeof(unsigned int));
  if(comp_counter == NULL)
  { 
    perror("Fail to allocate completion counter");
    exit(1); 
  } 
  for(chn = 0;chn < NUM_CHN;chn ++) 
    comp_counter[chn] = NUM_NODES - 1;
 
  voq_stat = (voq_stat_type **)malloc(NUM_NODES * sizeof(voq_stat_type *)); 
  if(voq_stat == NULL)
  { 
    perror("Fail to allocate VOQ status");
    exit(1); 
  } 
  voq_stat[0] = 
    (voq_stat_type *)malloc(NUM_NODES * NUM_CHN * sizeof(voq_stat_type));
  if(voq_stat[0] == NULL)
  { 
    perror("Fail to allocate VOQ status");
    exit(1); 
  } 
  for(node = 0;node < NUM_NODES;node ++)
  { 
    voq_stat[node] = voq_stat[0] + node * NUM_CHN;
    for(chn = 0;chn < NUM_CHN;chn ++)
      voq_stat[node][chn] = IDLE;
  } 

  //Initialize last busy recorders
  last_busy_cycle = (unsigned int **)malloc(NUM_NODES * sizeof(unsigned int *));
  if(last_busy_cycle == NULL)
  { 
    perror("Fail to allocate last busy recorders");
    exit(1); 
  }
  last_busy_cycle[0] = (unsigned int *)malloc(NUM_NODES * NUM_CHN * sizeof(unsigned int)); 
  if(last_busy_cycle[0] == NULL)
  { 
    perror("Fail to allocate last busy recorders");
    exit(1); 
  }
  for(node = 0;node < NUM_NODES;node ++)
  {
    last_busy_cycle[node] = last_busy_cycle[0] + node * NUM_CHN;
    for(chn = 0;chn < NUM_CHN;chn ++)
      last_busy_cycle[node][chn] = 0; 
  } 

  //Initialize node throughput counters
  node_throughputs = (unsigned int *)malloc(NUM_NODES * sizeof(unsigned int));
  if(node_throughputs == NULL)
  { 
    perror("Fail to allocate node throughputs");
    exit(1); 
  } 
  for(node = 0;node < NUM_NODES;node ++)
    node_throughputs[node] = 0; 

  //Initialize jittter bins
  bin_jitter = (unsigned int *)malloc(100 * sizeof(unsigned int));
  if(bin_jitter == NULL)
  { 
    perror("Fail to allocate jitter bins"); 
    exit(1); 
  }
  for(int i = 0;i < 100;i ++)
    bin_jitter[i] = 0; 

  //Initialize OSF 
  osf_init(); 
  //Reset statistics
  num_ejt_flits = 0; 
  num_inj_flits = 0; 
  average_flit_latency = 0; 
  accepted_throughput  = 0; 
} 
